/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.netbeans.editor; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Font; import java.awt.Color; import java.awt.Component; import java.awt.Rectangle; import java.awt.Shape; import java.awt.Insets; import java.text.AttributedCharacterIterator; import javax.swing.text.BadLocationException; import javax.swing.text.JTextComponent; /** * Class responsible for doing the all redrawing on the screen. * It's also responsible for doing syntax reanalyzing after operations * that change syntax highlighting. * There is only one instance for all documents and views. * * @author Miloslav Metelka * @version 1.00 */ class Drawer { /** Initial size of mark array in <CODE>DrawMarkRenderer</CODE>. */ private static final int DEFAULT_DRAW_MARK_RENDERER_SIZE = 20; /** Only one instance of drawer */ private static Drawer drawer; /** Prevent creation */ private Drawer() { } /** Get the static instance of drawer */ public static Drawer getDrawer() { if (drawer == null) { drawer = new Drawer(); } return drawer; } /** Draw on the specified area. * @param dg draw graphics through which the drawing is done * @param extUI extended UI to use * @param startPos position from which the drawing starts. It must be BOL * @param endPos position where the drawing stops. It must be either BOL of * @param targetPos position where the targetPosReached() method * of drawGraphics is called. This is useful for caret update or modelToView. * The Integer.MAX_VALUE can be passed to ignore that behavior. The -1 value * has special meaning there so that it calls targetPosReached() after each * character processed. This is used by viewToModel to find the position * for some point. */ void draw(DrawGraphics dg, ExtUI extUI, int startPos, int endPos, int baseX, int baseY, int targetPos) throws BadLocationException { // Some correctness tests at the begining if (dg == null || extUI == null || startPos < 0 || endPos < 0 || startPos > endPos || baseX < 0 || baseY < 0 ) { return; } /* try { int cnt = Utilities.getLineOffset(extUI.getDocument(), endPos) - Utilities.getLineOffset(extUI.getDocument(), startPos); if (cnt > 0) { System.out.println("startPos=" + startPos + ", endPos=" + endPos + ", line difference=" + cnt); // NOI18N } } catch (BadLocationException e) { e.printStackTrace(); } */ // System.out.println("Drawer.java:92 startPos=" + startPos + ", endPos=" + endPos + ", baseX=" + baseX + ", baseY=" + baseY); // NOI18N synchronized (extUI) { // lock operations manipulating draw layer chain BaseDocument doc = extUI.getDocument(); if (doc == null) { // no base-document available return; } SyntaxSeg.Slot slot = SyntaxSeg.getFreeSlot(); Syntax syntax = doc.getFreeSyntax(); DrawMarkRenderer drawMarkRenderer = new DrawMarkRenderer(); doc.readLock(); try { JTextComponent component = extUI.getComponent(); int docLen = doc.getLength(); int visCol = 0; // visual column (tabs expanded) on the line int x = baseX; int y = baseY; int ascent = extUI.ascents[0]; int spaceWidth = 0; // display width of actual space character Coloring defaultColoring = extUI.getDefaultColoring(); Font compFont = defaultColoring.getFont(); Color compBackColor = defaultColoring.getBackColor(); Color compForeColor = defaultColoring.getForeColor(); Font previousFont = compFont; // used when calling targetPosReached() int tabSize = doc.getTabSize(); int pos = startPos; // actual painting position boolean lastBuffer = false; // last syntax segment buffer in document boolean endDocDraw = false; // paint normal line at very end of doc int widestWidth = 0; // widest x coordinate encountered during paint DrawContextImpl ctx = new DrawContextImpl(); boolean targetAll = (targetPos == -1); // all chars are targets boolean contDraw = true; // flag indicating whether draw should continue int lineNum = 0; // current line number int lineCnt = 0; // number of lines drawn char[] lineNumChars = null; Coloring lineNumColoring = null; Font lnFont = null; Color lnBackColor = null; Color lnForeColor = null; Graphics graphics = dg.getGraphics(); boolean lazyLineNum = false; int debugFragCnt = 0; // !!! int debugUpdateCnt = 0; // !!! if (graphics != null) { if (extUI.renderingHints != null) { ((Graphics2D)graphics).setRenderingHints(extUI.renderingHints); } if (extUI.textLimitLineVisible) { // draw limit line int chw = FontMetricsCache.getFontMetrics(extUI.getDefaultColoring().getFont(), graphics).stringWidth("x"); int lineX = baseX + extUI.textLimitWidth * chw; graphics.setColor(extUI.textLimitLineColor); Rectangle clip = graphics.getClipBounds(); // graphics.drawRect(lineX, clip.y, 1, clip.height); graphics.drawLine(lineX, clip.y, lineX, clip.y + clip.height); } } dg.setJoinTokens(extUI.fixedFont); // create buffer for showing line numbers if (extUI.lineNumberVisible && dg.supportsLineNumbers()) { try { lineNum = Utilities.getLineOffset(doc, pos) + 1; } catch (BadLocationException e) { if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N e.printStackTrace(); } } lineNumColoring = extUI.getColoring(Settings.LINE_NUMBER_COLORING); if (lineNumColoring == null) { lineNumColoring = defaultColoring; // no number coloring found } lnFont = lineNumColoring.getFont(); if (lnFont == null) { lnFont = compFont; } lnBackColor = lineNumColoring.getBackColor(); if (lnBackColor == null) { lnBackColor = compBackColor; } lnForeColor = lineNumColoring.getForeColor(); if (lnForeColor == null) { lnForeColor = compForeColor; } lineNumChars = new char[Math.max(extUI.lineNumberMaxDigitCnt, 1)]; if (graphics == null) { lazyLineNum = true; } } // Initialize draw context ctx.pos = startPos; ctx.drawStartPos = startPos; ctx.drawEndPos = endPos; ctx.extUI = extUI; ctx.foreColor = compForeColor; ctx.backColor = compBackColor; ctx.font = compFont; ctx.bol = true; // draw must always start at line begin // Init draw graphics dg.init(ctx); // INIT ALL LAYERS BEFORE THE PAINT BEGINS DrawLayer[] layerArray = extUI.getDrawLayerList().currentLayers(); int activeLayerEndIndex = 0; for (int i = 0; i < layerArray.length; i++) { layerArray[i].init(ctx); // init all layers } int updatePos = Integer.MAX_VALUE; // next status update position int layerUpdatePos = updatePos; // position of next layer update for (int i = 0; i < layerArray.length; i++) { // update status of all layers DrawLayer l = layerArray[i]; l.updateStatus(ctx, null); if (l.nextUpdateStatusPos > pos && l.nextUpdateStatusPos < layerUpdatePos ) { layerUpdatePos = l.nextUpdateStatusPos; } if (l.active) { activeLayerEndIndex = i + 1; // assign end of active layers } } updatePos = layerUpdatePos; // GET ALL THE DRAW MARKS IN DRAW AREA THROUGH RENDERER drawMarkRenderer.setRange(startPos, endPos); doc.op.renderMarks(drawMarkRenderer); // no synch needed int markInd = 0; // index of current (next) draw mark in array MarkFactory.DrawMark mark = null; // current draw mark boolean markUpdate = false; // update status is because of mark int markPos = Integer.MAX_VALUE; // position of actual draw mark // Get current draw mark if (drawMarkRenderer.rangeMarkCnt > 0) { mark = drawMarkRenderer.rangeMarkArray[markInd]; markPos = drawMarkRenderer.rangePosArray[markInd++]; if (markPos < layerUpdatePos) { updatePos = markPos; markUpdate = true; } else { updatePos = layerUpdatePos; } } // Prepare syntax scanner and then cycle through all the syntax segments doc.op.prepareSyntax(slot, syntax, doc.op.getLeftSyntaxMark(pos), pos, endPos - pos); syntax.setLastBuffer(true); // always set to handle even non-complete lines ctx.buffer = slot.array; dg.setBuffer(slot.array); // CYCLE THROUGH ALL THE TOKENS FOUND IN THE BUFFER ------------------- do { int tokenID = syntax.nextToken(); if (tokenID == Syntax.EOT) { // end of text area if (!ctx.eol) { // force EOL tokenID = Syntax.EOL; endDocDraw = true; } else { // there was already EOL if (pos == docLen && !endDocDraw) { endDocDraw = true; } else { break; // break this cycle (and also outer one) } } } // Get the token type and docColorings String tokenName = syntax.getTokenName(tokenID); Coloring c = extUI.getColoring(tokenName); if (c == null) { c = defaultColoring; } // Get the token ctx.tokenStart = syntax.getTokenOffset(); ctx.tokenLen = syntax.getTokenLength(); int drawnLen = 0; // the length of current token that was already drawn int fragLen = 0; // length of current token fragment int blankWidth = 0; // display width of blank space for WS tokens if (ctx.bol) { // if we are on the line begining spaceWidth = extUI.defaultSpaceWidth; // possibly print line numbers at begining of each line if (lazyLineNum) { int i = Math.max(lineNumChars.length - 1, 0); int n = lineNum; do { lineNumChars[i--] = (char)('0' + (n % 10)); n /= 10; } while (n != 0 && i >= 0); while (i >= 0) { lineNumChars[i--] = ' '; } dg.setBuffer(lineNumChars); dg.setForeColor(lnForeColor); dg.setBackColor(lnBackColor); dg.setFont(lnFont); dg.drawChars(0, lineNumChars.length, 0, y, extUI.lineNumberWidth, extUI.charHeight, extUI.lineNumberAscent, false); dg.setBuffer(slot.array); lineNum++; } } // PROCESS ALL THE FRAGMENTS OF ONE TOKEN ---------------------------- do { // Fill in the draw context ctx.pos = pos; ctx.foreColor = compForeColor; ctx.backColor = compBackColor; ctx.font = compFont; ctx.eol = (tokenID == Syntax.EOL); // Check for status updates in planes at the begining of this fragment int nextPos = Integer.MAX_VALUE; while (updatePos == pos) { debugUpdateCnt++; if (markUpdate) { // update because of draw mark // means no-mark update yet performed activeLayerEndIndex = 0; for (int i = 0; i < layerArray.length; i++) { DrawLayer l = layerArray[i]; if (l.getName().equals(mark.layerName) && (mark.isDocumentMark() || extUI == mark.getExtUI()) ) { l.updateStatus(ctx, mark); if (l.nextUpdateStatusPos > pos && l.nextUpdateStatusPos < layerUpdatePos ) { layerUpdatePos = l.nextUpdateStatusPos; } } if (l.active) { activeLayerEndIndex = i + 1; } } // Get next mark if (markInd < drawMarkRenderer.rangeMarkCnt) { mark = drawMarkRenderer.rangeMarkArray[markInd]; markPos = drawMarkRenderer.rangePosArray[markInd++]; } else { // no more draw marks mark = null; markPos = Integer.MAX_VALUE; } // Check next update position if (markPos < layerUpdatePos) { updatePos = markPos; } else { markUpdate = false; updatePos = layerUpdatePos; } } else { // update because nextUpdateStatusPos set in some layer layerUpdatePos = Integer.MAX_VALUE; activeLayerEndIndex = 0; for (int i = 0; i < layerArray.length; i++) { DrawLayer l = layerArray[i]; if (l.nextUpdateStatusPos == pos) { l.updateStatus(ctx, null); } if (l.nextUpdateStatusPos > pos && l.nextUpdateStatusPos < layerUpdatePos ) { layerUpdatePos = l.nextUpdateStatusPos; } if (l.active) { activeLayerEndIndex = i + 1; } } if (pos == markPos) { // mark on this position markUpdate = true; } else { // no marks on this position, set new update pos updatePos = layerUpdatePos; if (markPos < updatePos) { updatePos = markPos; markUpdate = true; } } } } // COMPUTE CURRENT FRAGMENT (of token) LENGTH ---------------------- int fragStart = ctx.tokenStart + drawnLen; fragLen = Math.min(updatePos - pos, ctx.tokenLen - drawnLen); // check whether there are no tabs in the fragment and possibly shrink boolean wsFrag = false; boolean tabsInFrag = false; // whether there are tabs inside fragment if (fragLen > 0 && slot.array[fragStart] == ' ') { // space is first in the token int nwInd = Analyzer.getFirstNonSpace(slot.array, fragStart, fragLen); if (nwInd != -1) { // not whole fragment is whitespace fragLen = nwInd - fragStart; } wsFrag = true; } else { // space is not first char in the token int tabOffset = Analyzer.getFirstTab(slot.array, fragStart, fragLen); if (tabOffset >= 0) { // tab inside fragment if (tabOffset == fragStart) { // tab is first char in fragment tabsInFrag = true; int nwInd = Analyzer.getFirstNonWhite(slot.array, fragStart, fragLen); if (nwInd != -1) { // not whole fragment is whitespace fragLen = nwInd - fragStart; } wsFrag = true; } else { // tab somewhere inside fragment fragLen = tabOffset - fragStart; // shrink fragment size } } } int spaceLen = fragLen; debugFragCnt++; // Go through all layers to update draw context int layerEndInd = Math.min(activeLayerEndIndex, layerArray.length); for (int i = 0; i < layerEndInd; i++) { DrawLayer l = layerArray[i]; if (l.active) { // syntax layer special handling follows if (DrawLayerFactory.SYNTAX_LAYER_NAME == l.getName()) { c.apply(ctx); } else { // regular layer l.updateContext(ctx); } } } // HANDLE POSSIBLE WHITE SPACE EXPANSION AND COMPUTE DISPLAY WIDTH int fragWidth = 0; // display width of the fragment characters if (wsFrag) { // white space fragment /* spaceWidth is not updated for white space tokens, so that * spaceWidth stays computed from the last non-WS token drawn. * Although this is slightly logically incorrect, it allows * concatenating of the tokens with the whitespace between them. * For example 'private static final int' can be drawn * by calling drawChars() only once instead of four times. */ spaceLen = Analyzer.getColumn(slot.array, fragStart, fragLen, tabSize, visCol) - visCol; fragWidth = spaceLen * spaceWidth; } else { // non-WS token // update spaceWidth spaceWidth = extUI.spaceWidths[ctx.font.getStyle()]; // Compute fragWidth if (fragLen > 0) { if (extUI.superFixedFont) { fragWidth = fragLen * extUI.spaceWidths[0]; } else if (extUI.fixedFont) { fragWidth = fragLen * extUI.spaceWidths[ctx.font.getStyle()]; } else { fragWidth = FontMetricsCache.getFontMetrics(ctx.font, component).charsWidth( slot.array, fragStart, fragLen); } } } // POSSIBLY FILL THE BACKGROUND WITH SPECIAL COLOR ---------------- boolean emptyLine = false; blankWidth = fragWidth; if (ctx.eol) { // special handling for EOL dg.flush(); do { blankWidth = 0; if (ctx.bol) { // empty line found if (!emptyLine) { // not yet processed layerEndInd = Math.min(activeLayerEndIndex, layerArray.length); for (int i = 0; i < layerEndInd; i++) { DrawLayer l = layerArray[i]; if (l.active && l.extendEmptyLine) { emptyLine = true; // for at least one layer l.updateContext(ctx); } } if (emptyLine) { // count only if necessary blankWidth = spaceWidth / 2; // display half of char } } else { // already went through the cycle once for empty line emptyLine = false; } } if (!emptyLine) { // EOL and currently not servicing empty line boolean extendEOL = false; layerEndInd = Math.min(activeLayerEndIndex, layerArray.length); for (int i = 0; i < layerEndInd; i++) { DrawLayer l = layerArray[i]; if (l.active && l.extendEOL) { extendEOL = true; // for at least one layer l.updateContext(ctx); } } if (extendEOL) { blankWidth = component.getWidth(); } } if (blankWidth > 0) { dg.setBackColor(ctx.backColor); dg.fillRect(x, y, blankWidth, extUI.charHeight); if (emptyLine) { x += blankWidth; } } } while (emptyLine); } else { // DRAW REGULAR FRAGMENT dg.setBackColor(ctx.backColor); if (!wsFrag) { dg.setForeColor(ctx.foreColor); dg.setFont(ctx.font); if (!extUI.superFixedFont && graphics != null) { if (extUI.fixedFont) { ascent = extUI.ascents[ctx.font.getStyle()]; } else { ascent = (int)(FontMetricsCache.getFontMetrics(ctx.font, graphics).getAscent() * extUI.lineHeightCorrection); } } } dg.drawChars(tabsInFrag ? -1 : fragStart, spaceLen, x, y, fragWidth, extUI.charHeight, ascent, wsFrag); } // CHECK WHETHER TARGET POS WAS REACHED --------------------------- if (fragLen == 0 && (targetPos == pos || targetAll)) { char ch = (tokenID == Syntax.EOL) ? '\n' : ' '; contDraw = dg.targetPosReached(pos, ch, x, y, extUI.spaceWidths[0], ctx, previousFont); } else if (targetAll) { int prevWidth = 0; int curWidth; int baseIndex = fragStart; for (int i = 0; contDraw && i < fragLen; i++) { if (tabsInFrag) { // handle fragment with tabs in a different way int spcCount = Analyzer.getColumn(slot.array, fragStart, i + 1, tabSize, visCol) - visCol; curWidth = spcCount * spaceWidth; } else { // no tabs in a fragment if (extUI.fixedFont) { curWidth = (i + 1) * spaceWidth; } else { curWidth = FontMetricsCache.getFontMetrics(ctx.font, component).charsWidth( slot.array, baseIndex, i + 1); } } contDraw = dg.targetPosReached(pos + i, slot.array[baseIndex + i], x + prevWidth, y, curWidth - prevWidth, ctx, (i == 0) ? previousFont : ctx.font); prevWidth = curWidth; } } else if (targetPos < pos + fragLen && pos <= targetPos) { int curWidth; int prevWidth = 0; int baseIndex = fragStart; int i = (targetPos - pos); if (extUI.fixedFont) { // fixed of superFixed font prevWidth = i * spaceWidth; curWidth = (i + 1) * spaceWidth; } else { // variable width font if (i > 0) { if (tabsInFrag) { // handle fragment with tabs in a different way int spcCount = Analyzer.getColumn(slot.array, fragStart, i, tabSize, visCol) - visCol; prevWidth = spcCount * spaceWidth; } else { // no tabs in a fragment prevWidth = FontMetricsCache.getFontMetrics(ctx.font, component).charsWidth( slot.array, baseIndex, i); } } if (tabsInFrag) { // handle fragment with tabs in a different way int spcCount = Analyzer.getColumn(slot.array, fragStart, i + 1, tabSize, visCol) - visCol; curWidth = spcCount * spaceWidth; } else { // no tabs in a fragment curWidth = FontMetricsCache.getFontMetrics(ctx.font, component).charsWidth( slot.array, baseIndex, i + 1); } } contDraw = dg.targetPosReached(pos + i, slot.array[baseIndex + i], x + prevWidth, y, curWidth - prevWidth, ctx, (i == 0) ? previousFont : ctx.font); } previousFont = ctx.font; // Update status of layers that need it at the end of line if (ctx.eol) { activeLayerEndIndex = 0; for (int i = 0; i < layerArray.length; i++) { DrawLayer l = layerArray[i]; if (l.updateStatusEOL) { l.updateStatus(ctx, null); if (l.nextUpdateStatusPos >= pos + fragLen && l.nextUpdateStatusPos < updatePos ) { updatePos = l.nextUpdateStatusPos; markUpdate = false; } } if (l.active) { activeLayerEndIndex = i + 1; } } } // Move the variables to the next fragment in token pos += fragLen; drawnLen += fragLen; visCol += spaceLen; x += fragWidth; ctx.bol = false; } while(contDraw && drawnLen < ctx.tokenLen); // all fragments of token were drawn here // Update coordinates at the end of each line if (ctx.eol) { dg.eol(x, y); // sign EOL to DG widestWidth = Math.max(widestWidth, x); // update widest width visCol = 0; x = baseX; y += extUI.charHeight; ctx.bol = true; lineCnt++; } } while (contDraw); // cycle through all tokens inside buffer dg.setBuffer(null); dg.finish(); extUI.updateVirtualWidth(widestWidth + extUI.lineNumberWidth + extUI.spaceWidths[0]); // one char width for cursor if (graphics != null) { Rectangle bounds = extUI.getExtentBounds(); Rectangle clip = graphics.getClipBounds(); Insets textMargin = extUI.textMargin; if (extUI.lineNumberVisible && !lazyLineNum) { // draw line numbers now if (extUI.lineNumberVisible && dg.supportsLineNumbers()) { int numY = baseY; if (clip.x <= bounds.x + textMargin.left) { graphics.setColor(lnBackColor); if (lnBackColor != null && !compBackColor.equals(lnBackColor)) { graphics.fillRect(bounds.x, numY, extUI.lineNumberWidth, lineCnt * extUI.charHeight); } graphics.setColor(lnForeColor); graphics.setFont(lnFont); numY += extUI.lineNumberAscent; int lastDigit = Math.max(lineNumChars.length - 1, 0); int numX = bounds.x; if (extUI.lineNumberMargin != null) { numX += extUI.lineNumberMargin.left; } for (int j = 0; j < lineCnt; j++) { // draw all line numbers int n = lineNum + j; int i = lastDigit; do { lineNumChars[i--] = (char)('0' + (n % 10)); n /= 10; } while (n != 0 && i >= 0); while (i >= 0) { lineNumChars[i--] = ' '; } graphics.drawChars(lineNumChars, 0, lineNumChars.length, numX, numY); numY += extUI.charHeight; } } } } // Possibly clear margins graphics.setColor(compBackColor); int leftM = textMargin.left - extUI.lineNumberWidth; if (leftM > 0 && bounds.x > 0) { graphics.fillRect(bounds.x + extUI.lineNumberWidth, baseY, leftM, lineCnt * extUI.charHeight); } if (textMargin.right > 0) { graphics.fillRect(bounds.x + bounds.width - textMargin.right, baseY, textMargin.right, lineCnt * extUI.charHeight); } if (textMargin.top > 0 && clip.y < bounds.y + textMargin.top) { graphics.fillRect(bounds.x, bounds.y, bounds.width, textMargin.top); } int bY = bounds.y + bounds.height - textMargin.bottom; if (textMargin.bottom > 0 && clip.y + clip.height > bY) { graphics.fillRect(bounds.x, bY, bounds.width, textMargin.bottom); } } } finally { doc.releaseSyntax(syntax); SyntaxSeg.releaseSlot(slot); doc.readUnlock(); } } // synchronized on extUI } private final class DrawContextImpl implements DrawContext { Color foreColor; Color backColor; Font font; int pos; int drawStartPos; int drawEndPos; boolean bol; boolean eol; ExtUI extUI; char[] buffer; int token; int tokenStart; int tokenLen; public Color getForeColor() { return foreColor; } public void setForeColor(Color foreColor) { this.foreColor = foreColor; } public Color getBackColor() { return backColor; } public void setBackColor(Color backColor) { this.backColor = backColor; } public Font getFont() { return font; } public void setFont(Font font) { this.font = font; } public int getOffset() { return pos; } public int getDrawStartPos() { return drawStartPos; } public int getDrawEndPos() { return drawEndPos; } public boolean isBOL() { return bol; } public boolean isEOL() { return eol; } public ExtUI getExtUI() { return extUI; } public char[] getBuffer() { return buffer; } public int getToken() { return token; } public int getTokenStart() { return tokenStart; } public int getTokenLength() { return tokenLen; } } /** Draw graphics interface is used to enable various kinds of drawing. It's used * for drawing into classic graphics, painting the caret and printing. Its functions * are similair to subset of Graphics but there are few differences. */ interface DrawGraphics { /** Get foreground color */ public Color getForeColor(); /** Set foreground color */ public void setForeColor(Color foreColor); /** Get background color */ public Color getBackColor(); /** Set background color */ public void setBackColor(Color backColor); /** Get current font */ public Font getFont(); /** Set current font */ public void setFont(Font font); /** Get the graphics to determine whether this draws to a graphics. * This is useful for fast line numbering and others. */ public Graphics getGraphics(); /** Whether draw graphics supports displaying of line numbers. * If not line number displaying is not done. */ public boolean supportsLineNumbers(); /** Whether the draw graphics can use the optimization technique * that joins multiple tokens with the same font and color into one. * This can be turned off generally when using variable width fonts. */ public void setJoinTokens(boolean join); /** Initialize this draw graphics before drawing */ public void init(DrawContext ctx); /** Flush the cached information, so that there's no dependence * on buffer content. */ public void flush(); /** Called when whole drawing ends. Can be used to deallocate * some resources etc. */ public void finish(); /** Fill rectangle with specified color */ public void fillRect(int x, int y, int width, int height); /** Draw characters from the specified offset in the buffer * @param offset offset in the buffer for drawn text; if the text contains * tabs, then offset is set to -1 and length contains the full length * of the expanded tabs * @param length length of the text being drawn * @param x x coordinate * @param y y coordinate * @param textWidth width of the text being drawn in points * @param isWhite whether the text contains only ' ' or '\t' */ public void drawChars(int offset, int length, int x, int y, int width, int height, int ascent, boolean isWhite); /** Set character buffer from which the characters are drawn. */ public void setBuffer(char[] buffer); /** This method is called to notify this draw graphics in response * from targetPos parameter passed to draw(). * @param pos position reached. This has sense when targetPos is -1 * @param ch character at pos * @param x x painting position * @param y y painting position * @param charWidth visual width of the character ch * @param ctx current draw context containing * @param previousFont this is useful for caret painting to do correct * italicizing * @return whether the drawing should continue or not. If it returns * false it's guaranteed that this method will not be called again * and the whole draw() method will be stopped. */ public boolean targetPosReached(int pos, char ch, int x, int y, int charWidth, DrawContext ctx, Font previousFont); /** EOL encountered */ public void eol(int x, int y); } /** Parent of caret and view DGs. It only remembers * current color, font and buffer. */ static abstract class AbstractDG implements Drawer.DrawGraphics { /** Current foreground color */ Color foreColor; /** Current background color */ Color backColor; /** Current font */ Font font; /** Character buffer from which the data are drawn */ char[] buffer; public Color getForeColor() { return foreColor; } public void setForeColor(Color foreColor) { this.foreColor = foreColor; } public Color getBackColor() { return backColor; } public void setBackColor(Color backColor) { this.backColor = backColor; } public Font getFont() { return font; } public void setFont(Font font) { this.font = font; } public Graphics getGraphics() { return null; } public boolean supportsLineNumbers() { return false; } /** Tokens joining is ignored by default */ public void setJoinTokens(boolean join) { } public void init(DrawContext ctx) { } public void flush() { } public void finish() { } public void fillRect(int x, int y, int width, int height) { } public void drawChars(int offset, int length, int x, int y, int width, int height, int ascent, boolean isWhite) { } public void setBuffer(char[] buffer) { this.buffer = buffer; } public boolean targetPosReached(int pos, char ch, int x, int y, int charWidth, DrawContext ctx, Font previousFont) { return true; // shouldn't reach this place } public void eol(int x, int y) { } } /** Implementation of DrawGraphics to delegate to some Graphics. */ static final class GraphicsDG extends AbstractDG { Graphics graphics; /** Current graphics color */ Color gColor; /** Current graphics font */ Font gFont; /** Background color of component */ Color compBackColor; /** Start of the chars that were not drawn yet */ int startOffset = -1; /** End of the chars that were not drawn yet */ int endOffset; /** X coordinate where the drawing of chars should occur */ int x; /** Y coordinate where the drawing of chars should occur */ int y; int width; int height; int ascent; boolean joinTokens; int debugDCCnt; // drawChars() real call count !!! int debugFRCnt; // fillRect() real call count !!! GraphicsDG(Graphics graphics) { this.graphics = graphics; } public void init(DrawContext ctx) { JTextComponent c = ctx.getExtUI().getComponent(); compBackColor = c.getBackground(); gColor = graphics.getColor(); gFont = graphics.getFont(); debugDCCnt = 0; debugFRCnt = 0; } public void flush() { if (startOffset < 0) { return; } if (startOffset == endOffset) { startOffset = -1; return; } fillRect(x, y, width, height); if (foreColor != gColor) { graphics.setColor(foreColor); gColor = foreColor; } if (font != gFont) { graphics.setFont(font); gFont = font; } graphics.drawChars(buffer, startOffset, endOffset - startOffset, x, y + ascent); debugDCCnt++; startOffset = -1; } public void finish() { flush(); } public void setForeColor(Color foreColor) { if (!foreColor.equals(this.foreColor)) { flush(); this.foreColor = foreColor; } } public void setBackColor(Color backColor) { if (!backColor.equals(this.backColor)) { flush(); this.backColor = backColor; } } public void setFont(Font font) { if (!font.equals(this.font)) { flush(); this.font = font; } } public Graphics getGraphics() { return graphics; } public boolean supportsLineNumbers() { return true; } public void setJoinTokens(boolean join) { joinTokens = join; } public void fillRect(int x, int y, int width, int height) { if (width > 0) { if (!backColor.equals(compBackColor)) { if (backColor != gColor) { graphics.setColor(backColor); gColor = backColor; } graphics.fillRect(x, y, width, height); debugFRCnt++; } } } public void drawChars(int offset, int length, int x, int y, int width, int height, int ascent, boolean isWhite) { if (!joinTokens && startOffset >= 0) { // flush each painting when not joining flush(); } if (length >= 0) { if (offset < 0) { // has tabs inside flush(); fillRect(x, y, width, height); } else { // no tabs inside if (startOffset < 0) { if (isWhite) { // use fill for non-acumulated space fillRect(x, y, width, height); } else { // non-space text startOffset = offset; endOffset = offset + length; this.x = x; this.y = y; this.width = width; this.height = height; this.ascent = ascent; } } else { // already token before endOffset += length; this.width += width; } } } } public void setBuffer(char[] buffer) { flush(); this.buffer = buffer; startOffset = -1; } public void eol(int x, int y) { flush(); } } static class PrintDG extends AbstractDG { PrintContainer container; /** Whether there were some paints already on the line */ boolean lineInited; /** Construct the new print graphics * @param container print container to which the tokens * are added. */ public PrintDG(PrintContainer container) { this.container = container; } public boolean supportsLineNumbers() { return true; } public void drawChars(int offset, int length, int x, int y, int width, int height, int ascent, boolean isWhite) { if (length > 0) { char[] chars = new char[length]; if (offset < 0) { // tabs inside System.arraycopy(Analyzer.getSpacesBuffer(length), 0, chars, 0, length); } else { // no tabs inside System.arraycopy(buffer, offset, chars, 0, length); } container.add(chars, font, foreColor, backColor); } } public void eol(int x, int y) { if (!lineInited && container.initEmptyLines()) { drawChars(-1, 1, x, y, 1, 1, 1, true); } container.eol(); lineInited = false; } } /** This mark renderer is used to get two arrays - one for syntax marks * that were found in draw area and the other array holds positions * of these marks. */ static class DrawMarkRenderer extends DocMarks.Renderer { /** Array of marks */ MarkFactory.DrawMark rangeMarkArray[] = new MarkFactory.DrawMark[DEFAULT_DRAW_MARK_RENDERER_SIZE]; /** Array of positions */ int rangePosArray[] = new int[DEFAULT_DRAW_MARK_RENDERER_SIZE]; /** Total count of found marks (and also positions) */ int rangeMarkCnt; /** Starting position of mark search */ int startPos; /** End position of mark search */ int endPos; /** Get all syntax marks in a given range */ public void render() { int markCnt = getMarkCnt(); Mark mark = (Mark)getMarks().getLeftMark(startPos); int srcIndex = 0; int pos = 0; rangeMarkCnt = 0; Mark markArray[] = getMarkArray(); try { srcIndex = getMarkIndex(mark); pos = mark.getOffset(); } catch (InvalidMarkException e) { if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N e.printStackTrace(); } } if (pos < startPos) { // go to next mark mark = markArray[++srcIndex]; pos += getRelPos(mark); } while (pos <= endPos) { // will include even end-of-doc marks if (mark instanceof MarkFactory.DrawMark) { MarkFactory.DrawMark dm = (MarkFactory.DrawMark)mark; if (!dm.removeInvalid()) { // remove if not valid // Check array ranges if (rangePosArray.length < rangeMarkCnt + 1) { MarkFactory.DrawMark rma[] = new MarkFactory.DrawMark[ 2 * rangeMarkArray.length]; System.arraycopy(rangeMarkArray, 0, rma, 0, rangeMarkCnt); rangeMarkArray = rma; int rpa[] = new int[rma.length]; System.arraycopy(rangePosArray, 0, rpa, 0, rangeMarkCnt); rangePosArray = rpa; } rangeMarkArray[rangeMarkCnt] = (MarkFactory.DrawMark)mark; rangePosArray[rangeMarkCnt++] = pos; } } if (++srcIndex < markCnt) { mark = markArray[srcIndex]; pos += getRelPos(mark); } else { break; } } } /** Set the start and end positions between which * the renderer will operate. */ public void setRange(int startPos, int endPos) { this.startPos = startPos; this.endPos = endPos; } } } /* * Log * 52 Gandalf-post-FCS1.46.1.4 4/18/00 Miloslav Metelka cursor move fix * 51 Gandalf-post-FCS1.46.1.3 4/17/00 Miloslav Metelka fixed ghost tabs * 50 Gandalf-post-FCS1.46.1.2 4/13/00 Miloslav Metelka text-line now visible * 49 Gandalf-post-FCS1.46.1.1 4/5/00 Miloslav Metelka using FM caching * 48 Gandalf-post-FCS1.46.1.0 3/8/00 Miloslav Metelka * 47 Gandalf 1.46 1/13/00 Miloslav Metelka * 46 Gandalf 1.45 1/10/00 Miloslav Metelka * 45 Gandalf 1.44 12/28/99 Miloslav Metelka * 44 Gandalf 1.43 11/14/99 Miloslav Metelka * 43 Gandalf 1.42 11/10/99 Miloslav Metelka * 42 Gandalf 1.41 11/8/99 Miloslav Metelka * 41 Gandalf 1.40 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 40 Gandalf 1.39 10/10/99 Miloslav Metelka * 39 Gandalf 1.38 10/8/99 Miloslav Metelka Stability improvement * 38 Gandalf 1.37 10/8/99 Miloslav Metelka stability improvements * 37 Gandalf 1.36 10/6/99 Miloslav Metelka * 36 Gandalf 1.35 9/30/99 Miloslav Metelka * 35 Gandalf 1.34 9/16/99 Miloslav Metelka * 34 Gandalf 1.33 9/15/99 Miloslav Metelka * 33 Gandalf 1.32 9/10/99 Miloslav Metelka * 32 Gandalf 1.31 8/17/99 Miloslav Metelka * 31 Gandalf 1.30 7/29/99 Miloslav Metelka * 30 Gandalf 1.29 7/21/99 Miloslav Metelka * 29 Gandalf 1.28 7/21/99 Miloslav Metelka * 28 Gandalf 1.27 7/20/99 Miloslav Metelka * 27 Gandalf 1.26 7/2/99 Miloslav Metelka * 26 Gandalf 1.25 6/29/99 Miloslav Metelka Scrolling and patches * 25 Gandalf 1.24 6/25/99 Miloslav Metelka from floats back to ints * 24 Gandalf 1.23 6/24/99 Miloslav Metelka Drawing improved * 23 Gandalf 1.22 6/22/99 Miloslav Metelka * 22 Gandalf 1.21 6/8/99 Miloslav Metelka * 21 Gandalf 1.20 6/4/99 Miloslav Metelka removed debug * 20 Gandalf 1.19 6/1/99 Miloslav Metelka * 19 Gandalf 1.18 6/1/99 Miloslav Metelka * 18 Gandalf 1.17 5/21/99 Miloslav Metelka * 17 Gandalf 1.16 5/18/99 Miloslav Metelka patched printing * 16 Gandalf 1.15 5/15/99 Miloslav Metelka fixes * 15 Gandalf 1.14 5/13/99 Miloslav Metelka * 14 Gandalf 1.13 5/7/99 Miloslav Metelka line numbering and fixes * 13 Gandalf 1.12 5/5/99 Miloslav Metelka * 12 Gandalf 1.11 4/23/99 Miloslav Metelka Undo added and internal * improvements * 11 Gandalf 1.10 4/8/99 Miloslav Metelka * 10 Gandalf 1.9 4/8/99 Miloslav Metelka * 9 Gandalf 1.8 4/5/99 Ian Formanek Removed debug print * 8 Gandalf 1.7 4/1/99 Miloslav Metelka * 7 Gandalf 1.6 3/30/99 Miloslav Metelka * 6 Gandalf 1.5 3/27/99 Miloslav Metelka * 5 Gandalf 1.4 3/23/99 Miloslav Metelka * 4 Gandalf 1.3 3/18/99 Miloslav Metelka * 3 Gandalf 1.2 2/13/99 Miloslav Metelka * 2 Gandalf 1.1 2/9/99 Miloslav Metelka * 1 Gandalf 1.0 2/3/99 Miloslav Metelka * $ */